import { FileInfo, MainOptions, SrcObj } from './types';
import { getRelatedPath } from '../../utils/file.util';

const axios = require('axios').default;
const cheerio = require('cheerio');
const urlTool = require('url');
const fs = require('fs');
const pathTool = require('path');
const DIST_PATH = pathTool.resolve(__dirname, '../../dist');
let FetchFileCache: Record<string, boolean> = {};
let HTMLFileCache: Record<string, boolean> = {};
let needRecursionFun: MainOptions['needRecursion'] = () => false;
let writeFilePathFun: MainOptions['writeFilePath'] = (url: string) => url;
let needFetchFileFun: MainOptions['needFetchFile'] = () => true;
let BASE_URL = '/';
let COUNT = 0;
let MAX_HTML_COUNT = 100;

function resetVariable() {
  FetchFileCache = {};
  HTMLFileCache = {};
  COUNT = 0;
  MAX_HTML_COUNT = 100;
  needRecursionFun = () => false;
  writeFilePathFun = (url: string) => url;
  BASE_URL = '/';
  needFetchFileFun = () => true;
}

function getNextTargetPath(curTarget: string, restStr: string) {
  if (restStr.startsWith('/')) {
    return restStr;
  }
  if (curTarget.endsWith('/')) {
    return curTarget + restStr;
  }
  return curTarget + '/' + restStr;
}

function getFetchUrl(url: string, originUrl?: string) {
  let fetchUrl = url.trim();
  if (fetchUrl.startsWith('http')) {
    return fetchUrl;
  }
  if (fetchUrl.startsWith('//')) {
    return 'https:' + fetchUrl;
  }
  if (fetchUrl.startsWith('/')) {
    return BASE_URL + fetchUrl;
  }
  if (!originUrl) {
    return BASE_URL + '/' + fetchUrl;
  }
  return getRelatedPath(pathTool.join(BASE_URL, originUrl), fetchUrl);
}

async function getFileCntFromUrl(url: string, originUrl?: string): Promise<string> {
  const trimUrl = url.trim();
  if (FetchFileCache[trimUrl]) {
    return '';
  }
  const fetchUrl = getFetchUrl(trimUrl, originUrl);
  const resp = await axios.get(fetchUrl);
  if (!needRecursionFun(trimUrl)) {
    FetchFileCache[trimUrl] = true;
  }
  return resp.data;
}


function getHtmlSrc(html: string): SrcObj {
  if (!html) {
    return {
      links: [],
      aSrcs: [],
      all: [],
      scripts: [], imgs: []
    };
  }
  const $ = cheerio.load(html);
  const linkElements = $('link');
  const scriptElements = $('script');
  const imgElements = $('img');
  const aElements = $('a');
  const links = linkElements.map((i, ele) => ele.attribs['href']).get();
  const scripts = scriptElements.map((i, ele) => ele.attribs['src']).get();
  const imgs = imgElements.map((i, ele) => ele.attribs['src']).get().filter(item => {
    // 过滤掉base64图片
    return item.length < 640;
  });
  const aSrcs = aElements.map((i, ele) => ele.attribs['href']).get();
  return {
    links,
    scripts,
    imgs,
    aSrcs,
    all: [...links, ...scripts, ...imgs, ...aSrcs],
  };
}

async function writeFileDist(fileInfos: FileInfo[]) {
  fileInfos = fileInfos.filter(item => (item.path.startsWith('/') && item.content)).map(item => {
    return {
      path: pathTool.resolve(DIST_PATH, '.' + (writeFilePathFun(item.path) || item.path)),
      content: item.content,
    };
  });
  const writePromiseArr = fileInfos.map(item => {
    const targetDistPath = item.path;
    return fs.promises.mkdir(pathTool.dirname(targetDistPath), { recursive: true }).then(() => {
      return fs.promises.writeFile(targetDistPath.split('?')[0], item.content).catch(err => {
        console.log('errWriteFile:', targetDistPath);
      });
    }).catch(err => {
      console.log('errMkdir:', pathTool.dirname(targetDistPath));
      return;
    });
  });
  return Promise.all(writePromiseArr);
}

async function fetchTargetRes(target: string) {
  if (HTMLFileCache[target]) {
    return;
  }
  HTMLFileCache[target] = true;
  COUNT++;
  console.log(COUNT + '次start');
  if (COUNT > MAX_HTML_COUNT) {
    throw new Error('递归次数超出限制');
  }
  const html = await getFileCntFromUrl(target).catch(() => {
    console.log('errGetHTML:', getFetchUrl(target), target);
  });
  if (!html) {
    return;
  }
  const { all, aSrcs } = getHtmlSrc(html);
  const resFileUrls = all.filter((item) => needFetchFileFun(item));
  const fileInfoPromiseArr = resFileUrls.map(link => getFileCntFromUrl(link, target).then((cnt) => ({
    path: link,
    content: cnt,
  }), (err) => {
    console.log('errGetResFile:', getFetchUrl(link, target), link);
    return {
      path: link,
      content: '',
    };
  }));
  const fileInfos = await Promise.all(fileInfoPromiseArr);
  await writeFileDist([{
    path: target,
    content: html
  }, ...fileInfos]);
  const restHtmlArr = aSrcs.map(url => url.trim()).filter(url => {
    if (HTMLFileCache[url]) {
      return false;
    }
    return needRecursionFun(url);
  });
  if (restHtmlArr.length) {
    console.log('剩下的：', restHtmlArr);
    for (const restStr of restHtmlArr) {
      const nextPath = getNextTargetPath(target, restStr);
      console.log(nextPath + '准备开始');
      await fetchTargetRes(nextPath);
    }
  }
}

// !注意：main函数不能多异步，否则会导致全局数据混乱
export function main(urlPath: string, options: MainOptions = {}) {
  const urlParseObject = new urlTool.URL(urlPath);
  BASE_URL = urlParseObject.origin;
  if (options.needRecursion) {
    needRecursionFun = options.needRecursion;
  }
  if (options.writeFilePath) {
    writeFilePathFun = options.writeFilePath;
  }
  if (options.recursionDepth) {
    MAX_HTML_COUNT = options.recursionDepth;
  }
  if (options.needFetchFile) {
    needFetchFileFun = options.needFetchFile;
  }
  return fetchTargetRes(urlParseObject.pathname.trim()).finally(() => {
    resetVariable();
  });
}
